import { Observable, Subscription, Observer } from 'rxjs';
import { BaseManager } from './../libs/rxjs/cc3/BaseManager';
import { _decorator, color, director, System, Enum } from 'cc';
import { SocketDataTool, UnpackData } from '../panda/net/SocketDataTool';
import { nullifySubscription } from '../libs/rxjs/utils';
import { Interval, Timer } from '../libs/rxjs/cc3/CreatorSchedule';
import { SocketPackTool } from '../panda/net/SocketPackTool';
import { ReactiveProperty } from '../libs/rxjs/cc3/ReactiveProperty';
import echo from '../protocol/echo.js';
import core from '../protocol/core.js';
import { Utils } from '../panda/utils/Utils';
import { CryptoTool } from '../panda/libs/cryptos/CryptoTool';
const { ccclass, property } = _decorator;

const IP = '127.0.0.1';
const PORT = '2345';
const AuthKey = '1234567890';
const HeartbeatInterval = 5;                // socket 心跳间隔时间
const HeartbeatTimeout = 20;               // socket 心跳超时时间
const HeartbeatCheckInterval = 1;         // socket 心跳验证间隔时间

export enum WSStatus {
    Disconnect,
    Connected,
}
Enum(WSStatus)

@ccclass('WebSocketManager')
export class WebSocketManager extends BaseManager {

    public static instance: WebSocketManager;
    static ID = 'websocket_system';
    get logColorOverride() { return color().fromHEX('#FFFFAA') };

    static registerSystem() {
        director.unregisterSystem(director.getSystem(WebSocketManager.ID));
        director.registerSystem(WebSocketManager.ID, wsm, System.Priority.MEDIUM);
        wsm.init();
    }

    onInitManager() {
        this.log('onInitManager', this.timestamp);
    }

    wsStatus = ReactiveProperty.Create(WSStatus.Disconnect);

    // socket心跳配置
    private heartbeatLastTS: number | undefined = undefined;                // socket 最后一次心跳时间
    private enterBackgroundTS: number | undefined = undefined;              // 切入后台时的时间
    private enterForegroundTS: number | undefined = undefined;              // 切回前景时的时间

    //设置3秒的超时时间
    private connectTimeOutTS: number = 3000;
    private connectTimeOutHandler: any = null;

    _socket: WebSocket = null;
    dataTool: SocketDataTool = null;
    logicID: number = null;

    subscription: Subscription = null;
    sub_timeout: Subscription = null;
    _observer: Observer<{ type: string, event: Event | CloseEvent | MessageEvent }> = null;
    connect() {
        this.close();
        this.subscription = new Observable((observer: Observer<{ type: string, event: Event | CloseEvent | MessageEvent }>) => {
            this._observer = observer;
            this.dataTool = new SocketDataTool();
            let host = "ws://" + IP;
            if (PORT !== undefined) {
                host = host + ":" + PORT;
            }
            let socket = new WebSocket(host);
            socket.binaryType = 'arraybuffer';
            this.sub_timeout = Timer(3).subscribe(_ => {
                this.log('连接超时');
                socket.close();
            });
            socket.onopen = (event: Event) => { observer.next({ type: 'onopen', event }) };
            socket.onclose = (event: CloseEvent) => {
                observer.next({ type: 'onclose', event });
                observer.complete();
            };
            socket.onerror = (event: Event) => {
                observer.next({ type: 'onerror', event });
                observer.error(event);
            };
            socket.onmessage = (event: MessageEvent) => {
                observer.next({ type: 'onmessage', event });
            }
            this._socket = socket;
            this.logicID = 1;
            return () => {
                nullifySubscription.call(this, 'sub_timeout');
                nullifySubscription.call(this, 'subscription');
                this._socket = null;
            }
        }).subscribe({
            next: _ => {
                this.log('@ws', _);
                switch (_.type) {
                    case 'onopen':
                        this.wsStatus.value = WSStatus.Connected;
                        this.sendAuth();
                        this.heartCheck();
                        nullifySubscription.call(this, 'sub_timeout');
                        break;
                    case 'onclose':
                        this.wsStatus.value = WSStatus.Disconnect;
                        nullifySubscription.call(this, 'sub_timeout');
                        break;
                    case 'onerror':
                        this.wsStatus.value = WSStatus.Disconnect;
                        nullifySubscription.call(this, 'sub_timeout');
                        break;
                    case 'onmessage':
                        this.heartbeatLastTS = Utils.getTimeStamp();
                        this.dataTool.recvData((_.event as MessageEvent).data);
                        let unpackData: UnpackData | undefined;
                        // 尝试解包
                        for (let i = 0; i < 300; i++) {
                            if (this.dataTool !== undefined) {
                                unpackData = this.dataTool.unpack()
                            }
                            if (unpackData === undefined) {
                                break;
                            }
                            let unpackHandleFunc = SocketPackTool.getUnpackHandleFunc(unpackData.protobufId);
                            if (unpackHandleFunc !== undefined) {
                                unpackHandleFunc(unpackData.pack);
                            }
                        }
                        break;
                }
            },
            error: err => {
                this.warn('@wserr', err);
            },
            complete: () => {
                this.log('@wscomplete');
            }
        });
    }

    send(packetId: number, pack: any, bIsAuth: boolean = false): void {
        if (!bIsAuth) {
            this.logicID++;
        }
        let data: ArrayBuffer;
        if (this.dataTool != null) {
            let packFunc = SocketPackTool.getPackFunc(packetId);
            if (packFunc != null && packFunc != null) {
                data = this.dataTool.pack(packetId, this.logicID, packFunc.encode(pack).finish());
            }
        }
        if (this._socket != null && data != null) {
            this._socket.send(data);
        }
    }

    sendAuth() {
        console.log("Info: SocketMgr.sendAuth.");
        let pack = new core.protocol.SSPacketAuth();
        pack.Timestamp = Utils.getTimeStamp();
        let str = pack.Timestamp + ";" + AuthKey;
        pack.AuthKey = CryptoTool.Md5(str);
        this.send(core.protocol.CoreBuiltinPacketID.PACKET_SS_AUTH, pack, true);
    }

    close() {
        nullifySubscription.call(this, 'sub_timeout');
        if (this._sub_heartcheck != null) {
            nullifySubscription.call(this, '_sub_heartcheck');
        }
        if (this._socket != null) {
            this._socket.close(1000, "Client active close !");
            this._observer.next({ type: 'onclose', event: null });
            this._observer.complete();
        }
        // nullifySubscription.call(this, 'subscription');
    }

    sendPing() {
        let pack = new echo.echo.CSPacketPing();
        pack.TimeStamb = Date.now();
        pack.Message = 'Hello World!' as any;
        this.send(echo.echo.PacketID.PACKET_CS_PING, pack);
    }

    _sub_heartcheck: Subscription = null;
    heartCheck() {
        if (this._sub_heartcheck != null) return;
        this.enterBackgroundTS = undefined;
        this.enterForegroundTS = undefined;
        this.heartbeatLastTS = Utils.getTimeStamp();
        this._sub_heartcheck = Interval(HeartbeatCheckInterval).subscribe(_ => {
            let ts = Utils.getTimeStamp();
            let delayTime: number = 0
            if (this.heartbeatLastTS !== undefined) {
                delayTime = ts - this.heartbeatLastTS;
            }

            // 如果从后台切为前台， 则丢弃切入后台时间
            if (this.enterBackgroundTS !== undefined && this.enterForegroundTS !== undefined) {
                delayTime -= this.enterForegroundTS - this.enterBackgroundTS;
            }

            // 如果距离上次心跳时间已经超时，则认为断网
            if (delayTime >= HeartbeatTimeout) {
                console.log("Warn: SocketMgr.heartCheck heartbeat timeout !!!");
                this.close();
                return;
            }

            // 如果可以再次心跳
            if (delayTime >= HeartbeatInterval) {
                console.log("发送心跳消息========");
                this.sendPing();
                // 如果从后台切回，立即心跳
            } else if (this.enterBackgroundTS !== undefined && this.enterForegroundTS !== undefined) {
                console.log("发送心跳消息========aaaaa");
                this.sendPing();
            }
        });
    }

}
globalThis.WebSocketManager = WebSocketManager;

export const wsm = WebSocketManager.instance = new WebSocketManager();